using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Security;
using Fluent.IO;
using Sidewinder.Core.Interfaces.Entities;
using Wolfpack.Core;
using Wolfpack.Core.Interfaces.Entities;
using Wolfpack.Core.Notification;

namespace Wolfpack.Contrib.Deployment.Exe
{
    public abstract class ExecuteDeploymentPublisherBase<T> : DeploymentPublisherBase<T>
        where T: ExecuteDeploymentConfig
    {
        public IProcessLauncher Launcher { get; set; }

        protected ExecuteDeploymentPublisherBase(T config) 
            : base(config)
        {
            Launcher = new ProcessLauncher();
        }

        protected override void PostDownloadAction(NotificationEvent triggerNotification, UpdaterContext updateContext, TimeSpan deployDuration)
        {
            var dlFolder = SmartLocation.GetLocation(Config.DownloadFolder);
            var cmd = Config.Command;

            if (!Path.Get(cmd).IsRooted)
            {
                cmd = Path.Get(dlFolder, Config.Command).FullPath;
            }

            Logger.Info("\tRunning cmd '{0}' in working folder '{1}' with args '{2}'", cmd, dlFolder, Config.Args);
            var psi = new ProcessStartInfo(cmd)
                          {
                              WorkingDirectory = dlFolder,
                              Arguments = Config.Args
                          };

            if (!string.IsNullOrWhiteSpace(Config.UserName) && !string.IsNullOrWhiteSpace(Config.Password))
            {
                if (!string.IsNullOrWhiteSpace(Config.Domain))
                    psi.Domain = Config.Domain;
                psi.UserName = Config.UserName;
                psi.Password = GetSecureString(Config.Password);
            }

            try
            {
                var timer = new Stopwatch();
                timer.Start();
                var retCode = Launcher.Launch(psi);
                timer.Stop();

                if (retCode != 0)
                {
                    OnProcessFailure(triggerNotification, updateContext, timer.Elapsed, retCode);
                    return;
                }

                OnProcessSuccessful(triggerNotification, updateContext, timer.Elapsed);
            }
            catch (Exception ex)
            {
                OnProcessError(triggerNotification, updateContext, ex);
            }           
        }

        protected virtual void OnProcessSuccessful(NotificationEvent triggerNotification, UpdaterContext updateContext, TimeSpan duration)
        {
            var msg = HealthCheckData.For(Identity, 
                "Successfully executed package {0} v{1}", updateContext.Updates[0].Target.Name,
                    updateContext.Updates[0].NewVersion)
                .AddTag(updateContext.Updates[0].Target.Name)
                .Succeeded();
            msg.Duration = duration;
            Messenger.Publish(NotificationRequestBuilder.For(Config.NotificationMode, msg).Build());
        }

        protected virtual void OnProcessFailure(NotificationEvent triggerNotification, UpdaterContext updateContext, TimeSpan duration, int errorcode)
        {
            var msg = HealthCheckData.For(Identity,
                                          "Failed to execute package {0} v{1}; ErrorCode={2}",
                                          updateContext.Updates[0].Target.Name,
                                          updateContext.Updates[0].NewVersion,
                                          errorcode)
                .AddProperty("ErrorCode", errorcode.ToString(CultureInfo.InvariantCulture))
                .Failed();
            Messenger.Publish(NotificationRequestBuilder.For(Config.NotificationMode, msg).Build());
        }

        protected virtual void OnProcessError(NotificationEvent triggerNotification, UpdaterContext updateContext, Exception ex)
        {
            throw new InvalidOperationException("Package execution error!", ex);
        }

        private static SecureString GetSecureString(string value)
        {
            var ss = new SecureString();
            value.ToCharArray().ToList().ForEach(ss.AppendChar);
            return ss;
        }
    }
}
